import { Injectable } from '@nestjs/common';
import Iot, * as $Iot from '@alicloud/iot20180120';
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
import { DbService } from '../../db/db.service';
import { Device } from '@app/db/entity/Device';
import CreateDto from './dto/create.dto';
import * as dayjs from 'dayjs';
import PageDto from './dto/page.dto';
import { FindOptionsWhere, In, Like } from 'typeorm';
import UpdateDto from './dto/update.dto';
import { Merchant } from '@app/db/entity/Merchant';
import WorkException from '../../exception/work.exception';
import { DeviceTypeService } from '../device-type/device-type.service';
import { DeviceType } from '@app/db/entity/DeviceType';
import { AlicloudService } from '@app/alicloud';
import SetParamsDto from './dto/setParams.dto';
import { ConfigureService } from '@app/configure';

@Injectable()
export class DeviceService {

	constructor(
		private readonly db:DbService,
        private readonly deviceTypeService:DeviceTypeService,
        private readonly alicloud:AlicloudService,
        private readonly config:ConfigureService,

	) {
		
	}


    async page(dto:PageDto) {
        let result:PaginationResult<Device & {merchant? : Merchant, deviceType? : DeviceType}> = {
            total : 0,
            rows : []
        }

        let where:FindOptionsWhere<Device> = {

        }



        if(dto.id) {
            const ids = dto.id.split(',').map(item => Number(item)).filter(item => !isNaN(item) && item > 0)
            if(ids.length == 1) {
                where.id = ids[0]
            }else if(ids.length > 1) {
                where.id = In(ids)
            }
        }

        if(dto.name) {
            where.name = Like('%' + dto.name + '%')
        }

        if(dto.merchantId) {
            where.merchantId = dto.merchantId
        }

        if(dto.connectStatus !== undefined) {
            where.connectStatus = dto.connectStatus
        }

        if(dto.deviceTypeId) {
            const deviceType = await this.db.deviceType.findOne({id : dto.deviceTypeId})
            if(!deviceType) return result

            const deviceTypeIds = [dto.deviceTypeId]

            const subRows = await this.db.deviceType.find({topId : deviceType.parentId == 0 ? dto.deviceTypeId : deviceType.topId})
            if(subRows.length > 0) {
                const subIds:number[] = []
                this.deviceTypeService.querySubCategory(subRows, subIds, dto.deviceTypeId)
                deviceTypeIds.push(...subIds)
            }

            where.deviceTypeId = In(deviceTypeIds)
            
        }


        result.total = await this.db.device.count(where)

        if(result.total > 0) {

            result.rows = await this.db.device.pagination(dto.pageNo, dto.pageSize, where, null, {id : 'DESC'})

            if(result.rows.length > 0) {

                const merchantIds:number[] = []
                const deviceTypeIds:number[] = []

                result.rows.forEach(row => {
                    if(row.merchantId > 0 && !merchantIds.includes(row.merchantId)) {
                        merchantIds.push(row.merchantId)
                    }

                    if(row.deviceTypeId > 0 && !deviceTypeIds.includes(row.deviceTypeId)) {
                        deviceTypeIds.push(row.deviceTypeId)
                    }
                })

                if(merchantIds.length > 0 || deviceTypeIds.length > 0) {
                    const merchants = await this.db.merchant.find({id : In(merchantIds)}, {id : true, name : true})
                    const deviceTypes = await this.db.deviceType.find({id : In(deviceTypeIds)}, {id : true, name : true})

                    if(merchants.length > 0 || deviceTypes.length > 0) {
                        result.rows.forEach(row => {
                            if(row.merchantId > 0) {
                                const merchant = merchants.find(item => item.id == row.merchantId)
                                if(merchant) {
                                    row.merchant = merchant
                                }
                            }

                            if(row.deviceTypeId > 0) {
                                const deviceType = deviceTypes.find(item => item.id == row.deviceTypeId)
                                if(deviceType) {
                                    row.deviceType = deviceType
                                }
                            }
                        })
                    }
                }
            }
        }

        return result
    }

    
    public async add(dto:CreateDto, memberId:number) :Promise<boolean>{
        const deviceType = await this.db.deviceType.findOne({id : dto.deviceTypeId}, ['id', 'aliProduct'])
        if(!deviceType || !deviceType.aliProduct) return false

        const time = dayjs().unix()

        const toAli = dto.toAli
        delete dto.toAli

        const count = dto.count
        delete dto.count

        const devices:KeyToValueType<Device>[] = []
        for(let i = 0; i < count; i ++) {
			devices.push({creatorId : memberId, createTime : time, ...dto})
		}
		const r = await this.db.device.insert(devices)
        if(r.affectedRows < 1) return false


        if(toAli) {
            const deviceNames = r.rows.map(row => String(row.id))

            if(deviceNames.length == 1) {
                await this.singleRegisterDevice(deviceType.aliProduct, deviceNames[0])
            }else {
                await this.batchRegisterDevice(deviceType.aliProduct, deviceNames)
            }
        }

        
        return true
    }

    public async singleRegisterDevice(productKey:string, deviceName:string) {
        const r = await this.alicloud.registerDevice(productKey, deviceName)
        if(!r) return

        if(r.success) {
            await this.db.device.update({id : Number(deviceName)}, {aliSecret : r.data.deviceSecret})
        }else {
            if(r.code == 'iot.device.AlreadyExistedDeviceName') { //已经存在
                const res = await this.alicloud.queryDeviceInfo(productKey,deviceName)
                if(res.success) {
                    await this.db.device.update({id : Number(deviceName)}, {aliSecret : res.data.deviceSecret, atAli : true})
                }
            }
        }
    }

	public async batchRegisterDevice(productKey:string, deviceNames:string[]) {
		const r = await this.alicloud.batchQueryDeviceDetail(productKey, deviceNames)
		if(!r) return
        
		if(r.success && r.data) {
			r.data.data.forEach(row => {
				deviceNames.splice(deviceNames.indexOf(row.deviceName), 1)
				this.db.device.update({id : Number(row.deviceName)}, {aliSecret : row.deviceSecret, atAli : true})
			})
		}

		if(deviceNames.length > 0) {
			const r = await this.alicloud.batchCheckDeviceNames(productKey, deviceNames)
            if(!r) return

			if(r.success && r.data) {
				const r0 = await this.alicloud.batchRegisterDeviceWithApplyId(productKey, r.data.applyId)

				setTimeout(async () => {
					const r1 = await this.alicloud.batchQueryDeviceDetail(productKey, deviceNames)
                    if(!r1) return
					if(r1.success && r1.data) {
						r1.data.data.forEach(row => {
							this.db.device.update({id : Number(row.deviceName)}, {aliSecret : row.deviceSecret, atAli : true})
						})
					}
				}, 2000)

			}
		}
	}


    async update(dto:UpdateDto) {
        const id = dto.id
        const row = await this.db.device.findOne({id : id})
        if(!row) return false

        const updateData:KeyToValueType<Device> = {}

        for(let k in row) {
            if(k in dto) {
                if(dto[k] != row[k]) {
                    updateData[k] = dto[k]
                }
            }
        }

        if(Object.keys(updateData).length == 0) return true

        if(updateData.merchantId != undefined) {
            updateData.stationId = 0
        }

        if(updateData.aliSecret !== undefined && updateData.aliSecret === '') {
            updateData.atAli = false
        }

        const r = await this.db.device.update({id : id}, updateData)

        return r == 1

    }

    async del(id:number, syncDelAli=false) {
        let row = await this.db.device.findOne({ id }, ['id', 'deviceTypeId'])
        if(!row) return false
        const r = await this.db.device.delete({id : id})
        if(r > 0 && syncDelAli) {
            const deviceType = await this.db.deviceType.findOne({id : row.deviceTypeId}, ['id', 'aliProduct'])
            this.alicloud.deleteDevice(deviceType.aliProduct, String(id))
        }

        return r
    }


    async manyDeviceRegisterToAli(deviceIds:number[]) {
        const devices = await this.db.device.find({id : In(deviceIds)}, ['id', 'deviceTypeId'])
        if(devices.length == 0) return true
        const deviceTypes = await this.db.deviceType.find({id : In(devices.map(item => item.deviceTypeId))}, ['id', 'aliProduct'])
        const dataMap:{[key:string] : {aliProduct:string; deviceNames:string[]}} = {

        }
        deviceTypes.forEach(item => {
            dataMap[item.id] = {
                aliProduct : item.aliProduct,
                deviceNames : []
            }
        })

        devices.forEach(item => {
            if(dataMap[item.deviceTypeId] != undefined) {
                dataMap[item.deviceTypeId].deviceNames.push(String(item.id))
            }
        })

        for(let k in dataMap) {
            const row = dataMap[k]
            if(row.deviceNames.length == 1) {
                await this.singleRegisterDevice(row.aliProduct, row.deviceNames[0])
            }else if(row.deviceNames.length > 1) {
                await this.batchRegisterDevice(row.aliProduct, row.deviceNames)
            }
        }

        return true
    }

    async bindMerchant(merchantId:number, deviceIds:number[]) {
        const devices = await this.db.device.find({id : In(deviceIds)}, ['id', 'merchantId'])
        if(devices.findIndex(item => item.merchantId != 0) >= 0) throw new WorkException('已绑定商户的设备请先解绑商户')
        await this.db.device.update({id : In(deviceIds)}, {merchantId})
        return true
    }

    async unbindMerchant(deviceIds:number[]) {
        await this.db.device.update({id : In(deviceIds)}, {merchantId : 0, stationId : 0})
        return true
    }

    async setParams(dto:SetParamsDto, merchantId:number) {
        const topicReg = '/?/${deviceName}/user/get'//this.config.get('ALI_BATCH_TO_DEVICE_TOPIC_REG')
        const params = await this.db.deviceParams.findOne({id : dto.id})
        console.log(typeof params.params)
        const content = JSON.stringify(params.params)
        const size = 100
        if(dto.range == 2) { //当前设备类型
            const deviceType = await this.db.deviceType.findOne({id : params.deviceTypeId}, {id : true, aliProduct : true})
            
            const where:FindOptionsWhere<Device> = {
                deviceTypeId : params.deviceTypeId
            }
            if(merchantId > 0) {
                where.merchantId = merchantId
            }
            
            let devices = await this.db.device.find(where, {id : true, connectStatus : true})
            devices = devices.filter(device => device.connectStatus)
            const len = devices.length
            if(len == 0) return
            const topic = topicReg.replace(/\?/, deviceType.aliProduct)
            for (let i = 0; i < len; i += size) {
                const deviceType = await this.db.deviceType.findOne({id : params.deviceTypeId}, {id : true, aliProduct : true})
                await this.alicloud.batchPub(deviceType.aliProduct, topic, devices.slice(i, i + size).map(item => String(item.id)), content)
            }
        }else if(dto.range == 3) {
            const deviceTypeIds:number[] = []
            const deviceType = await this.db.deviceType.findOne({id : params.deviceTypeId}, {id : true, aliProduct : true, parentId : true, topId : true})
            deviceTypeIds.push(deviceType.id)
            const deviceTypeRows = await this.db.deviceType.find({topId : deviceType.parentId == 0 ? deviceType.id : deviceType.topId}, {id : true, aliProduct : true, parentId : true, topId : true})
            if(deviceTypeRows.length > 0) {
                const subIds:number[] = []
                this.deviceTypeService.querySubCategory(deviceTypeRows, subIds, deviceType.id)
                deviceTypeIds.push(...subIds)
            }


            const where:FindOptionsWhere<Device> = {deviceTypeId : In(deviceTypeIds)}
            if(merchantId > 0) {
                where.merchantId = merchantId
            }


            let devices = await this.db.device.find(where, {id : true, deviceTypeId : true, connectStatus : true})
            devices = devices.filter(device => device.connectStatus)
            if(devices.length == 0) return
            deviceTypeRows.push(deviceType)
            const deviceMap:{[key:string] : string[]} = {}
            devices.forEach(device => {
                const id = String(device.id)
                if(!deviceMap[device.deviceTypeId]) {
                    deviceMap[device.deviceTypeId] = []
                }

                deviceMap[device.deviceTypeId].push(id)
            })
            

            for(let k in deviceMap) {
                const type = deviceTypeRows.find(item => item.id == Number(k))
                const deviceIds = deviceMap[k]
                const len = deviceIds.length
                const topic = topicReg.replace(/\?/, type.aliProduct)
                for (let i = 0; i < len; i += size) {
                    await this.alicloud.batchPub(type.aliProduct, topic, deviceIds.slice(i, i + size), content)
                }
            }
            
        }else if(dto.range == 1) {
            if(!dto.deviceIds) return

            const where:FindOptionsWhere<Device> = {id : In(dto.deviceIds)}
            if(merchantId > 0) {
                where.merchantId = merchantId
            }

            let devices = await this.db.device.find(where, {id : true, deviceTypeId : true, connectStatus : true})
            devices = devices.filter(device => device.connectStatus)
            if(devices.length == 0) return
            const deviceTypeIds:number[] = []
            devices.forEach(device => {
                if(!deviceTypeIds.includes(device.deviceTypeId)) {
                    deviceTypeIds.push(device.deviceTypeId)
                }
            })

            const deviceTypes = await this.db.deviceType.find({id : In(deviceTypeIds)}, {id : true, aliProduct : true})

            const deviceMap:{[key:string] : string[]} = {}
            devices.forEach(device => {
                const id = String(device.id)
                if(!deviceMap[device.deviceTypeId]) {
                    deviceMap[device.deviceTypeId] = []
                }
                deviceMap[device.deviceTypeId].push(id)
            })

            for(let k in deviceMap) {
                const type = deviceTypes.find(item => item.id == Number(k))
                const deviceIds = deviceMap[k]
                const len = deviceIds.length
                console.log(topicReg)
                const topic = topicReg.replace(/\?/, type.aliProduct)
                console.log(topic)
                for (let i = 0; i < len; i += size) {
                    await this.alicloud.batchPub(type.aliProduct, topic, deviceIds.slice(i, i + size), content)
                }
            }
        }
    }
}
